package main;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.IOException;

import javax.swing.JButton;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
/**
 * 
 * @author Peng
 *
 */
public class SudokuBorder extends Border implements ActionListener, KeyListener
{
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	private final int standardSIZE=9;
	private int[][] sudokuMatrix = new int[standardSIZE][standardSIZE];//sudoku matrix, contains the original matrix and the player inputs as the game progress.
	int[][] sudokuMatrixSolution = new int[standardSIZE][standardSIZE];//The solution matrix.
	private JTextField panelText[][] = new JTextField[standardSIZE][standardSIZE];//Text field used to get inputs from the player.
	
	private JMenuItem createYourOwnAction = new JMenuItem("Create Your Own");
	private boolean create=false;
	private JMenuItem ruleAction = new JMenuItem("Rule");
	private JButton hintButton = new JButton("Hint");
	private JButton solveButton = new JButton("Solve");
	private JButton checkButton = new JButton("Check");
	private JButton clearButton = new JButton("Clear");
	private String rule =	"Sudoku is a logic game played on a 9x9 grid.\n" +
							"The grid is divided into nine 3x3 subgrids.\n" +
							"In a new game, several seed numbers are provided.\n" +
							"Your task is to fill empty spaces with number 1\n" +
							"through 9.The rules for number placement are that\n" +
							"every row, column, and 3x3 square must contain\n" +
							"all of the numbers 1 through 9. In other words,\n" +
							"there should not have any duplicate numbers in\n" +
							"any row, column or 3x3 square. The game is\n" +
							"complete when each cell contains a legitimate\n" +
							"value.";
	private String problem; // The sudoku matrix problem in string form.
	String solution;		// The solution of the sudoku matrix in string from.
	private String backupProblem; //Back up sudoku matrix problem.
	private boolean solved=false;
	
	public SudokuBorder()
	{
		super("Sudoku");
		this.solution=Sudoku.sudokuStr;
		sudokuMatrixSolution=Sudoku.parseToInt(this.solution);
		this.problem=Sudoku.parseToStr(Sudoku.sudokuLevel(sudokuMatrixSolution, Sudoku.EASY));
		settingMenuBar();
		initialize();
		setBackupProblem();
		
	}
	
	public SudokuBorder(String problem)
	{
		super("Sudoku");
		this.problem=problem;
		settingMenuBar();
		initialize();
		setBackupProblem();
	}
	
	private void setBackupProblem()
	{
		this.backupProblem = Sudoku.parseToStr(Sudoku.sudokuSolution());
	}
	
	/**
	 * Initializes the sudoku border.
	 */
	private void initialize()
	{
		setBorderSize(standardSIZE,standardSIZE);
		solved=false;
		createBorder();
		setSudokuBorder(problem);
		mainPanel.setBackground(color2);
		setSize(500,500);
		setVisible(true);
	}

	
	/**
	 * Get the player's solution.
	 * @return the player's solution in string.
	 */
	private String getPlaySolution()
	{
		String s="";
		String txtfieldstr="";
		for(int r=0; r<standardSIZE; r++)
		{
			for(int c=0; c<standardSIZE; c++)
			{
				txtfieldstr=panelText[r][c].getText();
				if(txtfieldstr.length()==0)
					txtfieldstr="0";
				s=s+txtfieldstr;
			}
		}
		return s;
	}
	
	/**
	 * Sets the sudoku border with JLabels, JTextfields.
	 * @param s, the sudoku problem in string form.
	 */
	private void setSudokuBorder(String s)
	{
		char[] problem=s.toCharArray();
		int index=0;
		for(int r=0; r<standardSIZE; r++)
		{
			for(int c=0; c<standardSIZE; c++)
			{
				smallPanels[r][c].setBackground(color4);
				sudokuMatrix[r][c]=problem[index]-48; //ascii 0-9 is 48-57
				panelText[r][c] = new JTextField(1);
				panelText[r][c].setBackground(color4);
				panelText[r][c].addKeyListener(this);
				
				if(sudokuMatrix[r][c]==0)
				{
					smallPanels[r][c].add(panelText[r][c]);
					
				}
				else
				{
					panelLabel[r][c].setText(Integer.toString(sudokuMatrix[r][c]));
					panelText[r][c].setText(Integer.toString(sudokuMatrix[r][c]));
					panelLabel[r][c].setForeground(color3);	
				}
				index++;
			}
		}
	}
	
	/**
	 * load values into the text field, called when loading a saved game.
	 * @param s, the saved sudoku in string form.
	 */
	void loadPanelText(String s)
	{
		char[] userInput=s.toCharArray();
		int input;
		int index=0;
		for(int r=0; r<standardSIZE; r++)
		{
			for(int c=0; c<standardSIZE; c++)
			{
				input=userInput[index]-48; //ascii 0-9 is 48-57
				if(input != 0)
					panelText[r][c].setText(Integer.toString(input));
				
				index++;
			}
		}
	}
	
	/**
	 * Shows the solution.
	 */
	private void showSolution()
	{
		for(int r=0; r<standardSIZE; r++)
		{
			for(int c=0; c<standardSIZE; c++)
			{
				panelText[r][c].setText(Integer.toString(sudokuMatrixSolution[r][c]));
				smallPanels[r][c].remove(panelText[r][c]);
				panelLabel[r][c].setText(Integer.toString(sudokuMatrixSolution[r][c]));
				
			}
		}
		this.repaint();
	}
	
	/**
	 * Gets the current matrix.
	 * @return current matrix.
	 */
	private int[][] getCurrentMatrix()
	{
		int[][] currentMatrix=new int[standardSIZE][standardSIZE];
		for(int r=0; r<standardSIZE; r++)
		{
			for(int c=0; c<standardSIZE; c++)
			{
				if(panelText[r][c].getText().length()==1)
					currentMatrix[r][c]=Integer.parseInt(panelText[r][c].getText());
				else
					currentMatrix[r][c]=0;
			}
		}
		
		return currentMatrix;
	}
	
	/**
	 * Checks if the sudoku matrix is completed.
	 * @return true if it's completed, else false.
	 */
	private boolean isCompleted()
	{
		int[][] currentMatrix=getCurrentMatrix();
		for(int r=0; r<standardSIZE; r++)
		{
			for(int c=0; c<standardSIZE; c++)
			{
				if(currentMatrix[r][c]!=sudokuMatrixSolution[r][c])
					return false;
			}
		}
		
		return true;
	}
	
	/**
	 * Fills a empty text field with a legal digit.
	 */
	private void hint()
	{
		for(int r=0; r<standardSIZE; r++)
		{
			for(int c=0; c<standardSIZE; c++)
			{
				if(panelText[r][c].getText().length()==0)
				{
					panelText[r][c].setText(Integer.toString(sudokuMatrixSolution[r][c]));
					return;
				}
			}
		}
	}
	
	
	
	
	
   /**
    * Sets the menu bar.
    */
   private void settingMenuBar()
   {
	   menuBar.setBackground(color2);
	   
	   // Add the menubar to the frame   
	   setJMenuBar(menuBar);	   

	   //add the main menu.
	   menuBar.add(mainMenu);
	   menuBar.add(hintButton);
	   menuBar.add(solveButton);
	   menuBar.add(checkButton);
	   menuBar.add(clearButton);
	   
	   hintButton.addActionListener(this);
	   solveButton.addActionListener(this);
	   checkButton.addActionListener(this);
	   clearButton.addActionListener(this);
	   
	   
	   newAction.addActionListener(this);
	   saveAction.addActionListener(this);
	   createYourOwnAction.addActionListener(this);
	   ruleAction.addActionListener(this);
	   exitAction.addActionListener(this);
	   esayLevel.addActionListener(this);
	   medianLevel.addActionListener(this);
	   hardLevel.addActionListener(this);
	   
	   
       newAction.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_MASK));
       saveAction.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_MASK));
       createYourOwnAction.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK));
       ruleAction.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.CTRL_MASK));
       exitAction.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, InputEvent.CTRL_MASK));
	   
	   esayLevel.setSelected(true);
	   
	   //add menu items.
	   mainMenu.add(newAction);
	   mainMenu.add(saveAction);
	   mainMenu.add(levelMenu);
	   mainMenu.add(createYourOwnAction);
	   mainMenu.add(ruleAction);
	   mainMenu.addSeparator();
	   mainMenu.add(exitAction);
	   
	   levelMenu.add(esayLevel);
	   levelMenu.add(medianLevel);
	   levelMenu.add(hardLevel);
 
   }

   /**
    * New game initialization.
    * @param level
    */
   private void newGameInit(int level)
   {
	   this.remove(mainPanel);
	   this.solution=this.backupProblem;
	   sudokuMatrixSolution=Sudoku.parseToInt(this.solution);
	   this.problem=Sudoku.parseToStr(Sudoku.sudokuLevel(sudokuMatrixSolution, level));
	   initialize();
	   
   }
   /**
    * Starts a new game.
    */
	private void startNewGame()
	{
		solved=false;
		try {
			SaveLoadGame.removeSavedGame();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		
		if(esayLevel.isSelected())
		{
			newGameInit(Sudoku.EASY);
		}
		else if(medianLevel.isSelected())
		{
			newGameInit(Sudoku.MEDIAN);
		}
		else
		{
			newGameInit(Sudoku.HARD);
		}
		setBackupProblem();
	}
	
	@SuppressWarnings("deprecation")
	private void clearTextField()
	{
		for(int r=0; r<standardSIZE; r++)
		{
			for(int c=0; c<standardSIZE; c++)
			{
				//if there are 2 components, then there is a panelText in smallPanel[r][c]
				if(smallPanels[r][c].countComponents()==2)
					panelText[r][c].setText("");
			}
		}
		
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		// TODO Auto-generated method stub
		
		if(e.getSource() == this.hintButton)
		{
			hint();
		}
		if(e.getSource() == this.solveButton)
		{
			if(solved)
			{
				int n = JOptionPane.showConfirmDialog(
	                    this,
	                    "This Sudoku is already solved, do you want start a new Sudoku?",
	                    "new game?",
	                    JOptionPane.YES_NO_OPTION);
				if(n==JOptionPane.YES_OPTION)
				{
					startNewGame();
				}
			}
			else
			{
				if(create)
				{
					sudokuMatrixSolution = getCurrentMatrix();
					Sudoku.writeMatrix(sudokuMatrixSolution);
					if(Sudoku.isLegalMatrix(sudokuMatrixSolution))
					{
						if(Sudoku.solve(0, 0, sudokuMatrixSolution))
						{
							showSolution();
							solved=true;
							create=false;
						}
					}
					else
					{
						JOptionPane.showMessageDialog(this,
							    "You have ilegal values in your matrix!",
							    "error",
							    JOptionPane.ERROR_MESSAGE);
						
					}
					
					
				}
				else
				{
					int n = JOptionPane.showConfirmDialog(
		                    this,
		                    "Are you sure to unveil the solution?",
		                    "solve?",
		                    JOptionPane.YES_NO_OPTION);
					if(n==JOptionPane.YES_OPTION)
					{
						try {
							SaveLoadGame.removeSavedGame();
						} catch (IOException e1) {
							// TODO Auto-generated catch block
							e1.printStackTrace();
						}
						showSolution();
						solved=true;
					}
				}
			}
			
		}
		else if(e.getSource() == this.checkButton)
		{
			if(isCompleted())
			{
				JOptionPane.showMessageDialog(this, "Congratulations!!!\nYou have solved the puzzle.");
				showSolution();
				solved=true;
			}
			else
			{
				JOptionPane.showMessageDialog(this, "The puzzle isn't solved yet.");
			}
		}
		else if(e.getSource() == this.clearButton)
		{
			clearTextField();
		}
		else if(e.getSource() == this.newAction)
		{
			startNewGame();
			
		}
		else if(e.getSource() == this.saveAction)
		{
			try {
				SaveLoadGame.save(problem, this.solution, getPlaySolution());
			} catch (IOException ioexcept) {
				// TODO Auto-generated catch block
				ioexcept.printStackTrace();
			} finally
			{
				JOptionPane.showMessageDialog(this, "Game Saved.");
			}
		}
		else if(e.getSource() == this.createYourOwnAction)
		{
			this.remove(mainPanel);
			//this.solution=this.backupProblem;
			//sudokuMatrixSolution=Sudoku.parseToInt(this.solution);
			this.problem="000000000000000000000000000000000000000000000000000000000000000000000000000000000";
			sudokuMatrixSolution = Sudoku.parseToInt(this.problem);
			initialize();
			create=true;
		}
		else if(e.getSource() == this.ruleAction)
		{
			JOptionPane.showMessageDialog(this, rule);
		}
		else if(e.getSource() == this.exitAction)
		{
			int n = JOptionPane.showConfirmDialog(
                    this,
                    "Would you like to save the game before exit?",
                    "save game?",
                    JOptionPane.YES_NO_OPTION);
			if(n==JOptionPane.YES_OPTION)
			{
				try {
					SaveLoadGame.save(problem, this.solution, getPlaySolution());
				} catch (IOException ioexcept) {
					// TODO Auto-generated catch block
					ioexcept.printStackTrace();
				}
			}
			this.dispose();
		}
		else if(e.getSource() == this.esayLevel)
		{
			this.medianLevel.setSelected(false);
			this.hardLevel.setSelected(false);
		}
		else if(e.getSource() == this.medianLevel)
		{
			this.esayLevel.setSelected(false);
			this.hardLevel.setSelected(false);
		}
		else if(e.getSource() == this.hardLevel)
		{
			this.esayLevel.setSelected(false);
			this.medianLevel.setSelected(false);
		}
		
	}


	@Override
	public void keyPressed(KeyEvent arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void keyReleased(KeyEvent arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void keyTyped(KeyEvent e) {
		// TODO Auto-generated method stub
		char c = e.getKeyChar();
		JTextField t = (JTextField) e.getSource();
	    int l = t.getText().length();
	    if (!(l < 1))
	        e.consume();
	    
	    if(!Character.isDigit(c) || c=='0')
	    	e.consume();

	    return;
		
	}

}
